HJS

PickOne 타입 (하나의 속성만 허용)

1️⃣ 개요

Pick<T, K>은 객체 타입에서 원하는 키만 선택할 수 있도록 해주지만, 여러 키를 동시에 선택 가능합니다.
하지만 때로는 객체의 여러 속성 중 "정확히 하나만" 가질 수 있도록 강제하고 싶은 경우가 있습니다. 이럴 때 사용가능한 커스텀 유틸 타입을 만들 수 있습니다.

2️⃣ 왜 필요한가?

API나 함수에서 조건 중 하나만 입력받도록 강제하고 싶을 때 PickOne이 매우 유용합니다.

🧐 예시: 검색 조건

사용자가 사람을 검색할 때 이름, 이메일, 전화번호 중 하나의 조건만 사용 가능해야 한다고 가정

type SearchFilter = {
  name: string;
  email: string;
  phone: string;
};

type FilterInput = PickOne<SearchFilter>;

function searchUser1(filter: SearchFilter) {
  // 여러 조건이 입력되어도 타입 에러가 발생하지 않음
}

function searchUser2(filter: FilterInput) {
  // 정확히 하나의 조건만 입력되어야 함
}
searchUser1({ name: "Alice" });             // ✅ 허용
searchUser1({ email: "alice@test.com" });   // ✅ 허용
searchUser1({ phone: "010-1234-5678" });    // ✅ 허용

searchUser2({ name: "Alice" });             // ✅ 허용
searchUser2({ email: "alice@test.com" });   // ✅ 허용
searchUser2({ phone: "010-1234-5678" });    // ✅ 허용

searchUser1({ name: "Alice", email: "alice@test.com" }); // ❗ 의도와 다르게 타입 에러 없음
searchUser2({ name: "Alice", email: "alice@test.com" }); // ❌ 오류: 둘 다 있으면 안 됨

✔️ 이메일과 전화번호가 동시에 들어오면 API가 모호해지기 때문에, 타입 수준에서 명확하게 하나만 허용되도록 제한하는 것이 한정하고 의도를 명확이 전달할 수 있는 방법입니다.



3️⃣ PickOne<T> 타입 정의

type PickOne<T> = {
	[P in keyof T]: Record<P, P[T]> & Partial<Record<Exclude<keyof T, P>, undefined>>;
}[keyof T]

🔹 Record<P, T[P]> - 현재 Key만 포함한 객체 만들기

예: P = "name"이면

Record<"name", string> // => { name: string }

✔️ key P필수로 포함된 객체


🔹 Exclude<keyof T, P> - 현재 key를제외한 나머지 key 추출하기

예: T = {name, email, phone} 이고, P = "name" 이면

Exclude<"name" | "email" | "phone", "name"> 
// => "email" | "phone"

✔️ P를 제외한 나머지 키들만 추출


🔹 Record<Exclude<keyof T, P>, undefined> - 나머지 키들은 undefined만 허용

Record<"email" | "phone", undefined>
// => { email: undefined; phone: undefined }

🔹 Partial<Record<Exclude<keyof T, P>, undefined>> - 나머지 키들은 optional + undefined

Partial<{ email: undefined; phone: undefined }>
// => { email?: undefined; phone?: undefined }

✔️ 해당 하는 키들은 없거나 undefined만 가능


🔹 Record<P, T[P]> & Partial<...> 현재 키만 필수 + 나머지 키는 optional or undefined

예: P = "name"

{
  name: string;       // 필수
  email?: undefined;  // 없어도 되고, 있으면 undefined
  phone?: undefined;	// 없어도 되고, 있으면 undefined
}

🔹 [P in keyof T] ... [keyof T] - 위 구조를 모든 키마다 만들어서 유니온으로 묶기

{
  [P in keyof T]: ...
}[keyof T]

예: T = {name: string, email: string, phone: string}

type PickOne<T> =
  | { name: string; email?: undefined; phone?: undefined }
  | { name?: undefined; email: string; phone?: undefined }
  | { name?: undefined; email?: undefined; phone: string }

✔️ T의 각 key마다 하나씩만 필수로 있고, 나머지는 optional undefined인 구조로 된 유니온 타입이 만들어집니다.
✔️ 정확히 하나만 값이 있는 객체 구조가 됩니다.